#include "HiredisAsyncContext.h"

#include <event2/event.h>
#include <event2/thread.h>
#include <hiredis/adapters/libevent.h>

#define GLOG_USE_GLOG_EXPORT
#include <glog/logging.h>

HiredisAsyncContext::HiredisAsyncContext(const std::string &ip, uint16_t port) : m_ipaddr(ip), m_port(port)
{
    m_base    = nullptr;
    m_context = nullptr;
}

HiredisAsyncContext::~HiredisAsyncContext()
{
    if (m_context) {
        redisAsyncDisconnect(m_context);
        redisAsyncFree(m_context);
    }

    if (m_base) {
        event_base_free(m_base);
    }
}

void HiredisAsyncContext::connected(const redisAsyncContext *ctx, int status)
{
    if (status != REDIS_OK) {
        LOG(ERROR) << "redis connect error: " << ctx->errstr;
    } else {
        LOG(INFO) << "redis connected.";
    }
}

void HiredisAsyncContext::disconnected(const redisAsyncContext *ctx, int status)
{
    if (status != REDIS_OK) {
        LOG(ERROR) << "redis disconnect error: " << ctx->errstr;
    } else {
        LOG(INFO) << "redis disconnected.";
    }
}

bool HiredisAsyncContext::init()
{
    evthread_use_pthreads();
    m_base = event_base_new();
    if (!m_base) {
        LOG(ERROR) << "Create event base failed!";
        return false;
    }
    return true;
}

bool HiredisAsyncContext::connect()
{
    m_context = redisAsyncConnect(m_ipaddr.c_str(), m_port);
    if (!m_context) {
        LOG(ERROR) << "Create event base failed!";
        return false;
    }
    redisLibeventAttach(m_context, m_base);
    redisAsyncSetConnectCallback(m_context, connected);
    redisAsyncSetDisconnectCallback(m_context, disconnected);
    return true;
}

void HiredisAsyncContext::dispatch()
{
    int ret = event_base_dispatch(m_base);
    if (ret == -1) {
        LOG(ERROR) << "event_base_dispatch error occurred!";
    }

    if (m_context) {
        redisAsyncDisconnect(m_context);
        redisAsyncFree(m_context);
        m_context = nullptr;
    }
}

void HiredisAsyncContext::loopbreak()
{
    event_base_loopbreak(m_base);
}

bool HiredisAsyncContext::execute(redisCallbackFn *fn, void *privdata, const char *command, ...)
{
    va_list args;
    va_start(args, command);
    int ret = redisvAsyncCommand(m_context, fn, privdata, command, args);
    va_end(args);
    if (ret != 0) {
        return false;
    }
    return true;
}